Skip to content

Commit e4ddbd5

Browse files
Merge pull request #433 from CodeForAfrica/ft/hero-fixes
feat(hero): add promise tracking functionality and refactor status resolution
2 parents 4cad1ab + 2554276 commit e4ddbd5

File tree

6 files changed

+94
-111
lines changed

6 files changed

+94
-111
lines changed

src/app/(frontend)/[...slugs]/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { PoliticalEntityList } from "@/components/PoliticalEntityList";
1111
import { getTenantBySubDomain, getTenantNavigation } from "@/lib/data/tenants";
1212
import {
1313
getPoliticalEntitiesByTenant,
14-
getExtractionCountsForEntities,
14+
getPromiseCountsForEntities,
1515
} from "@/lib/data/politicalEntities";
1616

1717
type Args = {
@@ -44,7 +44,7 @@ export default async function Page(params: Args) {
4444

4545
if (!politicalEntity) {
4646
const fallbackPageSlugs = slugs.length > 0 ? slugs : ["index"];
47-
const extractionCounts = await getExtractionCountsForEntities(
47+
const promiseCounts = await getPromiseCountsForEntities(
4848
politicalEntities.map((entity) => entity.id)
4949
);
5050

@@ -55,7 +55,7 @@ export default async function Page(params: Args) {
5555
tenant={tenant}
5656
politicalEntities={politicalEntities}
5757
pageSlugs={fallbackPageSlugs}
58-
extractionCounts={extractionCounts}
58+
promiseCounts={promiseCounts}
5959
/>
6060
<Footer title={title} description={description} {...footer} />
6161
</>

src/blocks/Hero/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const Hero: Block = {
1313
en: "Tagline",
1414
fr: "Accroche",
1515
},
16+
defaultValue: "Tracking",
1617
admin: {
1718
description: {
1819
en: "Displayed alongside the political entity name in the hero.",
@@ -30,6 +31,12 @@ export const Hero: Block = {
3031
en: "Promise Label",
3132
fr: "Libellé des promesses",
3233
},
34+
admin: {
35+
description: {
36+
en: "Label displayed beside the total count of synced promises.",
37+
fr: "Libellé affiché à côté du nombre total de promesses synchronisées.",
38+
},
39+
},
3340
defaultValue: "promises",
3441
},
3542
{
@@ -65,6 +72,7 @@ export const Hero: Block = {
6572
en: "Profile Title Override",
6673
fr: "Remplacement du titre du profil",
6774
},
75+
defaultValue: "Campaign Promises made by",
6876
admin: {
6977
description: {
7078
en: "Optional text that replaces the political entity position shown above the last updated date.",

src/components/Hero/index.tsx

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getGlobalPayload } from "@/lib/payload";
33
import type {
44
HeroBlock,
55
PoliticalEntity,
6-
AiExtraction as AiExtractionDocument,
6+
Promise as PromiseDocument,
77
PromiseStatus,
88
} from "@/payload-types";
99
import { format } from "date-fns";
@@ -62,9 +62,7 @@ type StatusById = Map<string, PromiseStatus>;
6262

6363
type StatusSummaryMap = Map<string, HeroStatusSummary>;
6464

65-
type AiExtractionItem = NonNullable<
66-
AiExtractionDocument["extractions"]
67-
>[number];
65+
type PromiseItem = PromiseDocument;
6866

6967
const FALLBACK_GROUP_DEFINITIONS = [
7068
{
@@ -81,21 +79,30 @@ const FALLBACK_GROUP_DEFINITIONS = [
8179
},
8280
];
8381

84-
const resolveExtractionStatus = (
85-
extraction: AiExtractionItem,
86-
statusById: StatusById
82+
const resolvePromiseStatus = (
83+
promise: PromiseItem,
84+
statusById: StatusById,
85+
statusByLabel: Map<string, PromiseStatus>
8786
): PromiseStatus | null => {
88-
const statusRef = extraction.Status;
87+
const statusRef = promise.status;
8988

90-
if (!statusRef) {
91-
return null;
89+
if (statusRef) {
90+
if (typeof statusRef === "string") {
91+
const mapped = statusById.get(statusRef);
92+
if (mapped) {
93+
return mapped;
94+
}
95+
} else {
96+
return statusRef;
97+
}
9298
}
9399

94-
if (typeof statusRef === "string") {
95-
return statusById.get(statusRef) ?? null;
100+
const label = promise.statusLabel?.trim().toLowerCase();
101+
if (!label) {
102+
return null;
96103
}
97104

98-
return statusRef;
105+
return statusByLabel.get(label) ?? null;
99106
};
100107

101108
const formatUpdatedAt = (date: string): string => {
@@ -210,57 +217,55 @@ export const Hero = async ({ entitySlug, ...block }: HeroProps) => {
210217
statusDocs.map((status) => [status.id, status])
211218
);
212219
const statusSummaries = buildStatusSummaries(statusDocs);
220+
const statusByLabel = new Map<string, PromiseStatus>();
221+
statusDocs.forEach((status) => {
222+
const labelKey = status.label?.trim().toLowerCase();
223+
if (labelKey) {
224+
statusByLabel.set(labelKey, status);
225+
}
226+
227+
const meedanKey = status.meedanId?.trim().toLowerCase();
228+
if (meedanKey && !statusByLabel.has(meedanKey)) {
229+
statusByLabel.set(meedanKey, status);
230+
}
231+
});
213232

214-
const { docs: documentDocs } = await payload.find({
215-
collection: "documents",
233+
const { docs: promiseDocs } = await payload.find({
234+
collection: "promises",
216235
where: {
217236
politicalEntity: {
218237
equals: entity.id,
219238
},
220239
},
221240
limit: -1,
241+
depth: 1,
222242
});
223243

224-
const documentIds = documentDocs.map((doc) => doc.id);
244+
let totalPromises = 0;
225245

226-
const { docs: aiExtractionDocs } = documentIds.length
227-
? await payload.find({
228-
collection: "ai-extractions",
229-
where: {
230-
document: {
231-
in: documentIds,
232-
},
233-
},
234-
depth: 2,
235-
limit: -1,
236-
})
237-
: { docs: [] };
246+
for (const promise of promiseDocs as PromiseItem[]) {
247+
const status = resolvePromiseStatus(
248+
promise,
249+
statusById,
250+
statusByLabel
251+
);
238252

239-
let totalExtractions = 0;
240-
241-
for (const aiExtractionDoc of aiExtractionDocs) {
242-
const extractions = aiExtractionDoc.extractions ?? [];
243-
244-
for (const extraction of extractions) {
245-
const status = resolveExtractionStatus(extraction, statusById);
246-
247-
if (!status) {
248-
continue;
249-
}
250-
251-
const summary = statusSummaries.get(status.id);
252-
if (!summary) {
253-
continue;
254-
}
253+
if (!status) {
254+
continue;
255+
}
255256

256-
summary.count += 1;
257-
totalExtractions += 1;
257+
const summary = statusSummaries.get(status.id);
258+
if (!summary) {
259+
continue;
258260
}
261+
262+
summary.count += 1;
263+
totalPromises += 1;
259264
}
260265

261266
statusSummaries.forEach((summary) => {
262267
summary.percentage =
263-
totalExtractions > 0 ? (summary.count / totalExtractions) * 100 : 0;
268+
totalPromises > 0 ? (summary.count / totalPromises) * 100 : 0;
264269
});
265270

266271
const groups = buildChartGroups(block.chartGroups, statusSummaries);
@@ -306,7 +311,7 @@ export const Hero = async ({ entitySlug, ...block }: HeroProps) => {
306311
image: entityImage,
307312
},
308313
metrics: {
309-
total: totalExtractions,
314+
total: totalPromises,
310315
statuses: summaries,
311316
groups,
312317
},

src/components/PoliticalEntityList.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type PoliticalEntityListProps = {
2222
tenant: Tenant;
2323
politicalEntities: PoliticalEntity[];
2424
pageSlugs?: string[];
25-
extractionCounts?: Record<string, number>;
25+
promiseCounts?: Record<string, number>;
2626
};
2727

2828
const buildHref = (entity: PoliticalEntity, pageSlugs: string[] = []) => {
@@ -41,7 +41,7 @@ const buildHref = (entity: PoliticalEntity, pageSlugs: string[] = []) => {
4141
export const PoliticalEntityList = async ({
4242
politicalEntities,
4343
pageSlugs = [],
44-
extractionCounts = {},
44+
promiseCounts = {},
4545
}: PoliticalEntityListProps) => {
4646
const payload = await getGlobalPayload();
4747

@@ -74,7 +74,7 @@ export const PoliticalEntityList = async ({
7474
{politicalEntities.map(async (entity, index) => {
7575
const href = buildHref(entity, pageSlugs);
7676
const media = await resolveMedia(entity.image);
77-
const extractionCount = extractionCounts[entity.id] ?? 0;
77+
const promiseCount = promiseCounts[entity.id] ?? 0;
7878
const initials = entity.name
7979
.split(" ")
8080
.slice(0, 2)
@@ -132,9 +132,9 @@ export const PoliticalEntityList = async ({
132132
) : null}
133133
</Box>
134134
<Chip
135-
label={`${extractionCount} promise${extractionCount === 1 ? "" : "s"}`}
136-
color={extractionCount > 0 ? "primary" : "default"}
137-
variant={extractionCount > 0 ? "filled" : "outlined"}
135+
label={`${promiseCount} promise${promiseCount === 1 ? "" : "s"}`}
136+
color={promiseCount > 0 ? "primary" : "default"}
137+
variant={promiseCount > 0 ? "filled" : "outlined"}
138138
/>
139139
</Stack>
140140
</ListItemButton>

src/lib/data/politicalEntities.ts

Lines changed: 21 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { getGlobalPayload } from "@/lib/payload";
2-
import type { PoliticalEntity, Tenant } from "@/payload-types";
2+
import type {
3+
PoliticalEntity,
4+
Tenant,
5+
Promise as PromiseDoc,
6+
} from "@/payload-types";
37

48
const payload = await getGlobalPayload();
59

@@ -48,19 +52,23 @@ export const getPoliticalEntityBySlug = async (
4852
return docs[0];
4953
};
5054

51-
export const getExtractionCountsForEntities = async (
55+
export const getPromiseCountsForEntities = async (
5256
entityIds: string[]
5357
): Promise<Record<string, number>> => {
5458
if (entityIds.length === 0) {
5559
return {};
5660
}
5761

58-
const { docs: documents } = await payload.find({
59-
collection: "documents",
60-
depth: 0,
62+
const counts = entityIds.reduce<Record<string, number>>((acc, id) => {
63+
acc[id] = 0;
64+
return acc;
65+
}, {});
66+
67+
const { docs: promises } = await payload.find({
68+
collection: "promises",
6169
limit: -1,
70+
depth: 0,
6271
select: {
63-
id: true,
6472
politicalEntity: true,
6573
},
6674
where: {
@@ -70,56 +78,15 @@ export const getExtractionCountsForEntities = async (
7078
},
7179
});
7280

73-
const docToEntity = new Map<string, string>();
74-
const documentIds: string[] = [];
75-
76-
for (const doc of documents) {
77-
const entityRelation = doc.politicalEntity;
81+
for (const promise of promises as PromiseDoc[]) {
82+
const relation = promise.politicalEntity;
7883
const entityId =
79-
typeof entityRelation === "string" ? entityRelation : entityRelation?.id;
80-
if (!entityId) continue;
81-
docToEntity.set(doc.id, entityId);
82-
documentIds.push(doc.id);
83-
}
84-
85-
if (documentIds.length === 0) {
86-
return entityIds.reduce<Record<string, number>>((acc, id) => {
87-
acc[id] = 0;
88-
return acc;
89-
}, {});
90-
}
91-
92-
const { docs: aiExtractions } = await payload.find({
93-
collection: "ai-extractions",
94-
depth: 0,
95-
limit: -1,
96-
select: {
97-
document: true,
98-
extractions: true,
99-
},
100-
where: {
101-
document: {
102-
in: documentIds,
103-
},
104-
},
105-
});
106-
107-
const counts = entityIds.reduce<Record<string, number>>((acc, id) => {
108-
acc[id] = 0;
109-
return acc;
110-
}, {});
84+
typeof relation === "string" ? relation : relation?.id ?? null;
85+
if (!entityId) {
86+
continue;
87+
}
11188

112-
for (const extraction of aiExtractions) {
113-
const relation = extraction.document;
114-
const documentId = typeof relation === "string" ? relation : relation?.id;
115-
if (!documentId) continue;
116-
const entityId = docToEntity.get(documentId);
117-
if (!entityId) continue;
118-
const extractionsCount = Array.isArray(extraction.extractions)
119-
? extraction.extractions.length
120-
: 0;
121-
if (extractionsCount === 0) continue;
122-
counts[entityId] = (counts[entityId] ?? 0) + extractionsCount;
89+
counts[entityId] = (counts[entityId] ?? 0) + 1;
12390
}
12491

12592
return counts;

src/payload-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,9 @@ export interface HeroBlock {
448448
* Displayed alongside the political entity name in the hero.
449449
*/
450450
tagline?: string | null;
451+
/**
452+
* Label displayed beside the total count of synced promises.
453+
*/
451454
promiseLabel?: string | null;
452455
/**
453456
* Sentence that follows the promise count, for example 'tracked on PromiseTracker'.

0 commit comments

Comments
 (0)