Skip to content

Commit

Permalink
Merge pull request #2406 from zetkin/release-241205
Browse files Browse the repository at this point in the history
241205 Release
  • Loading branch information
richardolsson authored Dec 5, 2024
2 parents 50ff8f8 + 6eb0d5c commit 135a024
Show file tree
Hide file tree
Showing 78 changed files with 2,507 additions and 951 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
question: m.question,
})),
organization: { id: assignmentModel.orgId },
reporting_level: assignmentModel.reporting_level || 'household',
start_date: assignmentModel.start_date,
title: assignmentModel.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { AreaModel } from 'features/areas/models';
import {
CanvassAssignmentModel,
PlaceModel,
PlaceVisitModel,
PlaceVisitModelType,
} from 'features/canvassAssignments/models';
import {
ZetkinAssignmentAreaStatsItem,
Expand Down Expand Up @@ -88,6 +90,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
organization: {
id: assignmentModel.orgId,
},
reporting_level: assignmentModel.reporting_level || 'household',
start_date: assignmentModel.start_date,
title: assignmentModel.title,
},
Expand Down Expand Up @@ -181,6 +184,19 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {

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

const allPlaceVisits = await PlaceVisitModel.find({
canvassAssId: params.canvassAssId,
});

const visitsByPlaceId: Record<string, PlaceVisitModelType[]> = {};
allPlaceVisits.forEach((visit) => {
if (!visitsByPlaceId[visit.placeId]) {
visitsByPlaceId[visit.placeId] = [];
}

visitsByPlaceId[visit.placeId].push(visit);
});

uniqueAreas.forEach((area) => {
statsByAreaId[area.id] = {
areaId: area.id,
Expand Down Expand Up @@ -234,6 +250,25 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
}
});
});

const placeVisits = visitsByPlaceId[place.id] || [];
placeVisits.forEach((visit) => {
const numHouseholds = Math.max(
...visit.responses.map((response) =>
response.responseCounts.reduce((sum, count) => sum + count, 0)
)
);

const successfulResponse = visit.responses.find(
(response) => response.metricId == idOfMetricThatDefinesDone
);
const numSuccessful = successfulResponse?.responseCounts[0] || 0;

statsByAreaId[area.id].num_successful_visited_households +=
numSuccessful;
statsByAreaId[area.id].num_visited_households += numHouseholds;
statsByAreaId[area.id].num_visited_places += 1;
});
}
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
question: metric.question,
})),
organization: { id: orgId },
reporting_level: canvassAssignmentModel.reporting_level || 'household',
start_date: canvassAssignmentModel.start_date,
title: canvassAssignmentModel.title,
};
Expand All @@ -66,7 +67,13 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
await mongoose.connect(process.env.MONGODB_URL || '');

const payload = await request.json();
const { metrics: newMetrics, title, start_date, end_date } = payload;
const {
metrics: newMetrics,
title,
start_date,
end_date,
reporting_level,
} = payload;

if (newMetrics) {
// Find existing metrics to remove
Expand Down Expand Up @@ -121,7 +128,10 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
}
}
type UpdateFieldsType = Partial<
Pick<ZetkinCanvassAssignment, 'title' | 'start_date' | 'end_date'>
Pick<
ZetkinCanvassAssignment,
'title' | 'start_date' | 'end_date' | 'reporting_level'
>
>;

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

if (reporting_level) {
updateFields.reporting_level = reporting_level;
}

if (Object.prototype.hasOwnProperty.call(payload, 'end_date')) {
updateFields.end_date = end_date;
}
Expand Down Expand Up @@ -165,6 +179,7 @@ export async function PATCH(request: NextRequest, { params }: RouteMeta) {
question: metric.question,
})),
organization: { id: orgId },
reporting_level: model.reporting_level || 'household',
start_date: model.start_date,
title: model.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
organization: {
id: model.orgId,
},
reporting_level: model.reporting_level || 'household',
start_date: model.start_date,
title: model.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export async function GET(request: NextRequest, { params }: RouteMeta) {
organization: {
id: assignmentModel.orgId,
},
reporting_level: assignmentModel.reporting_level || 'household',
start_date: assignmentModel.start_date,
title: assignmentModel.title,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import mongoose from 'mongoose';
import { NextRequest, NextResponse } from 'next/server';

import {
CanvassAssignmentModel,
PlaceVisitModel,
} from 'features/canvassAssignments/models';
import asCanvasserAuthorized from 'features/canvassAssignments/utils/asCanvasserAuthorized';

type RouteMeta = {
params: {
canvassAssId: string;
orgId: string;
};
};

export async function GET(request: NextRequest, { params }: RouteMeta) {
const canvassAssId = params.canvassAssId;
return asCanvasserAuthorized(
{
orgId: params.orgId,
request: request,
},
async ({ orgId }) => {
await mongoose.connect(process.env.MONGODB_URL || '');

const assignmentModel = await CanvassAssignmentModel.find({
_id: canvassAssId,
orgId: orgId,
});

if (!assignmentModel) {
return NextResponse.json({ error: {} }, { status: 404 });
}

const visitModels = await PlaceVisitModel.find({
canvassAssId: canvassAssId,
});

return NextResponse.json({
data: visitModels.map((model) => ({
canvassAssId: model.canvassAssId,
id: model._id.toString(),
personId: model.personId,
placeId: model.placeId,
responses: model.responses,
timestamp: model.timestamp,
})),
});
}
);
}

export async function POST(request: NextRequest, { params }: RouteMeta) {
const canvassAssId = params.canvassAssId;
return asCanvasserAuthorized(
{
orgId: params.orgId,
request: request,
},
async ({ orgId, personId }) => {
await mongoose.connect(process.env.MONGODB_URL || '');

const assignmentModel = await CanvassAssignmentModel.find({
_id: canvassAssId,
orgId: orgId,
});

if (!assignmentModel) {
return NextResponse.json({ error: {} }, { status: 404 });
}

const payload = await request.json();

const visit = new PlaceVisitModel({
canvassAssId,
personId: personId,
placeId: payload.placeId,
responses: payload.responses,
timestamp: new Date().toISOString(),
});

await visit.save();

return NextResponse.json({
data: {
canvassAssId: visit.canvassAssId,
id: visit._id.toString(),
personId: visit.personId,
placeId: visit.placeId,
responses: visit.responses,
timestamp: visit.timestamp,
},
});
}
);
}
2 changes: 2 additions & 0 deletions src/app/beta/orgs/[orgId]/canvassassignments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
campId: payload.campaign_id,
metrics: payload.metrics || [],
orgId: orgId,
reporting_level: payload.reporting_level || 'household',
title: payload.title,
});

Expand All @@ -81,6 +82,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
question: metric.question,
})),
organization: { id: orgId },
reporting_level: model.reporting_level || 'household',
start_date: model.start_date,
title: model.title,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
orgId: params.orgId,
request: request,
},
async ({ orgId }) => {
async ({ orgId, personId }) => {
await mongoose.connect(process.env.MONGODB_URL || '');

const payload = await request.json();
Expand All @@ -33,6 +33,7 @@ export async function POST(request: NextRequest, { params }: RouteMeta) {
id: new mongoose.Types.ObjectId().toString(),
missionAccomplished: payload.missionAccomplished,
noteToOfficial: payload.noteToOfficial,
personId: personId,
responses: payload.responses || [],
timestamp: payload.timestamp,
},
Expand Down
1 change: 1 addition & 0 deletions src/app/beta/users/me/canvassassignments/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function GET(request: NextRequest) {
organization: {
id: assignment.orgId,
},
reporting_level: assignment.reporting_level || 'household',
start_date: assignment.start_date,
title: assignment.title,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ZetkinCanvassAssignment } from '../types';
import MapControls from './MapControls';
import objToLatLng from 'features/areas/utils/objToLatLng';
import CanvassAssignmentMapOverlays from './CanvassAssignmentMapOverlays';
import useAllPlaceVisits from '../hooks/useAllPlaceVisits';

const useStyles = makeStyles(() => ({
'@keyframes ghostMarkerBounce': {
Expand Down Expand Up @@ -71,6 +72,10 @@ const CanvassAssignmentMap: FC<CanvassAssignmentMapProps> = ({
const classes = useStyles();
const places = usePlaces(assignment.organization.id).data || [];
const createPlace = useCreatePlace(assignment.organization.id);
const placeVisitList = useAllPlaceVisits(
assignment.organization.id,
assignment.id
);

const [selectedPlaceId, setSelectedPlaceId] = useState<string | null>(null);
const [isCreating, setIsCreating] = useState(false);
Expand Down Expand Up @@ -261,7 +266,12 @@ const CanvassAssignmentMap: FC<CanvassAssignmentMapProps> = ({
))}
</FeatureGroup>
{places.map((place) => {
const state = getVisitState(place.households, assignment.id);
const householdState = getVisitState(place.households, assignment.id);
const visited = placeVisitList.data?.some(
(visit) => visit.placeId == place.id
);

const state = visited ? 'all' : householdState;

const selected = place.id == selectedPlaceId;
const key = `marker-${place.id}-${selected.toString()}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Box, Button, IconButton } from '@mui/material';
import { Box, Button } from '@mui/material';
import { FC, useEffect, useState } from 'react';
import { makeStyles } from '@mui/styles';
import { KeyboardArrowUp } from '@mui/icons-material';

import { ZetkinCanvassAssignment, ZetkinPlace } from '../types';
import PlaceDialog from './PlaceDialog';
import { CreatePlaceCard } from './CreatePlaceCard';
import PageBaseHeader from './PlaceDialog/pages/PageBaseHeader';
import ContractedHeader from './PlaceDialog/ContractedHeader';

type Props = {
assignment: ZetkinCanvassAssignment;
Expand Down Expand Up @@ -58,11 +57,6 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
}
}, [selectedPlace]);

const numVisitedHouseholds =
selectedPlace?.households.filter((household) =>
household.visits.some((visit) => visit.canvassAssId == assignment.id)
).length ?? 0;

return (
<>
{!selectedPlace && !isCreating && (
Expand All @@ -78,7 +72,7 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
bottom: 0,
boxShadow: theme.shadows[20],
left: 0,
position: 'fixed',
position: 'absolute',
right: 0,
top: drawerTop,
transition: 'top 0.2s',
Expand All @@ -87,15 +81,7 @@ const CanvassAssignmentMapOverlays: FC<Props> = ({
>
{showViewPlaceButton && (
<Box onClick={() => setExpanded(true)} p={2}>
<PageBaseHeader
iconButtons={
<IconButton onClick={() => setExpanded(true)}>
<KeyboardArrowUp />
</IconButton>
}
subtitle={`${numVisitedHouseholds} / ${selectedPlace.households.length} households visited`}
title={selectedPlace.title || 'Untitled place'}
/>
<ContractedHeader assignment={assignment} place={selectedPlace} />
</Box>
)}
{selectedPlace && expanded && (
Expand Down
Loading

0 comments on commit 135a024

Please sign in to comment.