Skip to content

Commit 135a024

Browse files
Merge pull request #2406 from zetkin/release-241205
241205 Release
2 parents 50ff8f8 + 6eb0d5c commit 135a024

File tree

78 files changed

+2507
-951
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+2507
-951
lines changed

src/app/beta/orgs/[orgId]/canvassassignments/[canvassAssId]/areasgraph/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
7878
question: m.question,
7979
})),
8080
organization: { id: assignmentModel.orgId },
81+
reporting_level: assignmentModel.reporting_level || 'household',
8182
start_date: assignmentModel.start_date,
8283
title: assignmentModel.title,
8384
},

src/app/beta/orgs/[orgId]/canvassassignments/[canvassAssId]/areastats/route.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { AreaModel } from 'features/areas/models';
55
import {
66
CanvassAssignmentModel,
77
PlaceModel,
8+
PlaceVisitModel,
9+
PlaceVisitModelType,
810
} from 'features/canvassAssignments/models';
911
import {
1012
ZetkinAssignmentAreaStatsItem,
@@ -88,6 +90,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
8890
organization: {
8991
id: assignmentModel.orgId,
9092
},
93+
reporting_level: assignmentModel.reporting_level || 'household',
9194
start_date: assignmentModel.start_date,
9295
title: assignmentModel.title,
9396
},
@@ -181,6 +184,19 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
181184

182185
const statsByAreaId: Record<string, ZetkinAssignmentAreaStatsItem> = {};
183186

187+
const allPlaceVisits = await PlaceVisitModel.find({
188+
canvassAssId: params.canvassAssId,
189+
});
190+
191+
const visitsByPlaceId: Record<string, PlaceVisitModelType[]> = {};
192+
allPlaceVisits.forEach((visit) => {
193+
if (!visitsByPlaceId[visit.placeId]) {
194+
visitsByPlaceId[visit.placeId] = [];
195+
}
196+
197+
visitsByPlaceId[visit.placeId].push(visit);
198+
});
199+
184200
uniqueAreas.forEach((area) => {
185201
statsByAreaId[area.id] = {
186202
areaId: area.id,
@@ -234,6 +250,25 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
234250
}
235251
});
236252
});
253+
254+
const placeVisits = visitsByPlaceId[place.id] || [];
255+
placeVisits.forEach((visit) => {
256+
const numHouseholds = Math.max(
257+
...visit.responses.map((response) =>
258+
response.responseCounts.reduce((sum, count) => sum + count, 0)
259+
)
260+
);
261+
262+
const successfulResponse = visit.responses.find(
263+
(response) => response.metricId == idOfMetricThatDefinesDone
264+
);
265+
const numSuccessful = successfulResponse?.responseCounts[0] || 0;
266+
267+
statsByAreaId[area.id].num_successful_visited_households +=
268+
numSuccessful;
269+
statsByAreaId[area.id].num_visited_households += numHouseholds;
270+
statsByAreaId[area.id].num_visited_places += 1;
271+
});
237272
}
238273
});
239274
});

src/app/beta/orgs/[orgId]/canvassassignments/[canvassAssId]/route.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
4646
question: metric.question,
4747
})),
4848
organization: { id: orgId },
49+
reporting_level: canvassAssignmentModel.reporting_level || 'household',
4950
start_date: canvassAssignmentModel.start_date,
5051
title: canvassAssignmentModel.title,
5152
};
@@ -66,7 +67,13 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
6667
await mongoose.connect(process.env.MONGODB_URL || '');
6768

6869
const payload = await request.json();
69-
const { metrics: newMetrics, title, start_date, end_date } = payload;
70+
const {
71+
metrics: newMetrics,
72+
title,
73+
start_date,
74+
end_date,
75+
reporting_level,
76+
} = payload;
7077

7178
if (newMetrics) {
7279
// Find existing metrics to remove
@@ -121,7 +128,10 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
121128
}
122129
}
123130
type UpdateFieldsType = Partial<
124-
Pick<ZetkinCanvassAssignment, 'title' | 'start_date' | 'end_date'>
131+
Pick<
132+
ZetkinCanvassAssignment,
133+
'title' | 'start_date' | 'end_date' | 'reporting_level'
134+
>
125135
>;
126136

127137
const updateFields: UpdateFieldsType = {};
@@ -134,6 +144,10 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
134144
updateFields.start_date = start_date;
135145
}
136146

147+
if (reporting_level) {
148+
updateFields.reporting_level = reporting_level;
149+
}
150+
137151
if (Object.prototype.hasOwnProperty.call(payload, 'end_date')) {
138152
updateFields.end_date = end_date;
139153
}
@@ -165,6 +179,7 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
165179
question: metric.question,
166180
})),
167181
organization: { id: orgId },
182+
reporting_level: model.reporting_level || 'household',
168183
start_date: model.start_date,
169184
title: model.title,
170185
},

src/app/beta/orgs/[orgId]/canvassassignments/[canvassAssId]/sessions/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
7676
organization: {
7777
id: model.orgId,
7878
},
79+
reporting_level: model.reporting_level || 'household',
7980
start_date: model.start_date,
8081
title: model.title,
8182
},

src/app/beta/orgs/[orgId]/canvassassignments/[canvassAssId]/stats/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
8383
organization: {
8484
id: assignmentModel.orgId,
8585
},
86+
reporting_level: assignmentModel.reporting_level || 'household',
8687
start_date: assignmentModel.start_date,
8788
title: assignmentModel.title,
8889
},
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import mongoose from 'mongoose';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
import {
5+
CanvassAssignmentModel,
6+
PlaceVisitModel,
7+
} from 'features/canvassAssignments/models';
8+
import asCanvasserAuthorized from 'features/canvassAssignments/utils/asCanvasserAuthorized';
9+
10+
type RouteMeta = {
11+
params: {
12+
canvassAssId: string;
13+
orgId: string;
14+
};
15+
};
16+
17+
export async function GET(request: NextRequest, { params }: RouteMeta) {
18+
const canvassAssId = params.canvassAssId;
19+
return asCanvasserAuthorized(
20+
{
21+
orgId: params.orgId,
22+
request: request,
23+
},
24+
async ({ orgId }) => {
25+
await mongoose.connect(process.env.MONGODB_URL || '');
26+
27+
const assignmentModel = await CanvassAssignmentModel.find({
28+
_id: canvassAssId,
29+
orgId: orgId,
30+
});
31+
32+
if (!assignmentModel) {
33+
return NextResponse.json({ error: {} }, { status: 404 });
34+
}
35+
36+
const visitModels = await PlaceVisitModel.find({
37+
canvassAssId: canvassAssId,
38+
});
39+
40+
return NextResponse.json({
41+
data: visitModels.map((model) => ({
42+
canvassAssId: model.canvassAssId,
43+
id: model._id.toString(),
44+
personId: model.personId,
45+
placeId: model.placeId,
46+
responses: model.responses,
47+
timestamp: model.timestamp,
48+
})),
49+
});
50+
}
51+
);
52+
}
53+
54+
export async function POST(request: NextRequest, { params }: RouteMeta) {
55+
const canvassAssId = params.canvassAssId;
56+
return asCanvasserAuthorized(
57+
{
58+
orgId: params.orgId,
59+
request: request,
60+
},
61+
async ({ orgId, personId }) => {
62+
await mongoose.connect(process.env.MONGODB_URL || '');
63+
64+
const assignmentModel = await CanvassAssignmentModel.find({
65+
_id: canvassAssId,
66+
orgId: orgId,
67+
});
68+
69+
if (!assignmentModel) {
70+
return NextResponse.json({ error: {} }, { status: 404 });
71+
}
72+
73+
const payload = await request.json();
74+
75+
const visit = new PlaceVisitModel({
76+
canvassAssId,
77+
personId: personId,
78+
placeId: payload.placeId,
79+
responses: payload.responses,
80+
timestamp: new Date().toISOString(),
81+
});
82+
83+
await visit.save();
84+
85+
return NextResponse.json({
86+
data: {
87+
canvassAssId: visit.canvassAssId,
88+
id: visit._id.toString(),
89+
personId: visit.personId,
90+
placeId: visit.placeId,
91+
responses: visit.responses,
92+
timestamp: visit.timestamp,
93+
},
94+
});
95+
}
96+
);
97+
}

src/app/beta/orgs/[orgId]/canvassassignments/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
6363
campId: payload.campaign_id,
6464
metrics: payload.metrics || [],
6565
orgId: orgId,
66+
reporting_level: payload.reporting_level || 'household',
6667
title: payload.title,
6768
});
6869

@@ -81,6 +82,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
8182
question: metric.question,
8283
})),
8384
organization: { id: orgId },
85+
reporting_level: model.reporting_level || 'household',
8486
start_date: model.start_date,
8587
title: model.title,
8688
},

src/app/beta/orgs/[orgId]/places/[placeId]/households/[householdId]/visits/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
1818
orgId: params.orgId,
1919
request: request,
2020
},
21-
async ({ orgId }) => {
21+
async ({ orgId, personId }) => {
2222
await mongoose.connect(process.env.MONGODB_URL || '');
2323

2424
const payload = await request.json();
@@ -33,6 +33,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
3333
id: new mongoose.Types.ObjectId().toString(),
3434
missionAccomplished: payload.missionAccomplished,
3535
noteToOfficial: payload.noteToOfficial,
36+
personId: personId,
3637
responses: payload.responses || [],
3738
timestamp: payload.timestamp,
3839
},

src/app/beta/users/me/canvassassignments/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export async function GET(request: NextRequest) {
7676
organization: {
7777
id: assignment.orgId,
7878
},
79+
reporting_level: assignment.reporting_level || 'household',
7980
start_date: assignment.start_date,
8081
title: assignment.title,
8182
});

src/features/canvassAssignments/components/CanvassAssignmentMap.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ZetkinCanvassAssignment } from '../types';
2727
import MapControls from './MapControls';
2828
import objToLatLng from 'features/areas/utils/objToLatLng';
2929
import CanvassAssignmentMapOverlays from './CanvassAssignmentMapOverlays';
30+
import useAllPlaceVisits from '../hooks/useAllPlaceVisits';
3031

3132
const useStyles = makeStyles(() => ({
3233
'@keyframes ghostMarkerBounce': {
@@ -71,6 +72,10 @@ const CanvassAssignmentMap: FC<CanvassAssignmentMapProps> = ({
7172
const classes = useStyles();
7273
const places = usePlaces(assignment.organization.id).data || [];
7374
const createPlace = useCreatePlace(assignment.organization.id);
75+
const placeVisitList = useAllPlaceVisits(
76+
assignment.organization.id,
77+
assignment.id
78+
);
7479

7580
const [selectedPlaceId, setSelectedPlaceId] = useState<string | null>(null);
7681
const [isCreating, setIsCreating] = useState(false);
@@ -261,7 +266,12 @@ const CanvassAssignmentMap: FC<CanvassAssignmentMapProps> = ({
261266
))}
262267
</FeatureGroup>
263268
{places.map((place) => {
264-
const state = getVisitState(place.households, assignment.id);
269+
const householdState = getVisitState(place.households, assignment.id);
270+
const visited = placeVisitList.data?.some(
271+
(visit) => visit.placeId == place.id
272+
);
273+
274+
const state = visited ? 'all' : householdState;
265275

266276
const selected = place.id == selectedPlaceId;
267277
const key = `marker-${place.id}-${selected.toString()}`;

src/features/canvassAssignments/components/CanvassAssignmentMapOverlays.tsx

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { Box, Button, IconButton } from '@mui/material';
1+
import { Box, Button } from '@mui/material';
22
import { FC, useEffect, useState } from 'react';
33
import { makeStyles } from '@mui/styles';
4-
import { KeyboardArrowUp } from '@mui/icons-material';
54

65
import { ZetkinCanvassAssignment, ZetkinPlace } from '../types';
76
import PlaceDialog from './PlaceDialog';
87
import { CreatePlaceCard } from './CreatePlaceCard';
9-
import PageBaseHeader from './PlaceDialog/pages/PageBaseHeader';
8+
import ContractedHeader from './PlaceDialog/ContractedHeader';
109

1110
type Props = {
1211
assignment: ZetkinCanvassAssignment;
@@ -58,11 +57,6 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
5857
}
5958
}, [selectedPlace]);
6059

61-
const numVisitedHouseholds =
62-
selectedPlace?.households.filter((household) =>
63-
household.visits.some((visit) => visit.canvassAssId == assignment.id)
64-
).length ?? 0;
65-
6660
return (
6761
<>
6862
{!selectedPlace && !isCreating && (
@@ -78,7 +72,7 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
7872
bottom: 0,
7973
boxShadow: theme.shadows[20],
8074
left: 0,
81-
position: 'fixed',
75+
position: 'absolute',
8276
right: 0,
8377
top: drawerTop,
8478
transition: 'top 0.2s',
@@ -87,15 +81,7 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
8781
>
8882
{showViewPlaceButton && (
8983
<Box onClick={() => setExpanded(true)} p={2}>
90-
<PageBaseHeader
91-
iconButtons={
92-
<IconButton onClick={() => setExpanded(true)}>
93-
<KeyboardArrowUp />
94-
</IconButton>
95-
}
96-
subtitle={`${numVisitedHouseholds} / ${selectedPlace.households.length} households visited`}
97-
title={selectedPlace.title || 'Untitled place'}
98-
/>
84+
<ContractedHeader assignment={assignment} place={selectedPlace} />
9985
</Box>
10086
)}
10187
{selectedPlace && expanded && (

0 commit comments

Comments
 (0)