Prefill file fields with their file_name joined from CiviCRM

CiviCRM APIv4 file custom fields return a bare file id by default; an
extra '.file_name' join is required to get the human-readable filename.
Both the form prefill walk (lib/prefill.ts) and the report walk
(app/api/report/route.ts) now request '<civiField>.file_name' for every
file-type field alongside the primary value, and wrap the prefill into
a { id, file_name } object so downstream UI has both. Falls back to
file_name undefined when the join returns null (eg orphaned id).

The form's FilePriorIndicator already accepts the object shape, so it
now shows the filename inline. The report's FormattedValue gets a
matching case: renders the file_name string if present, falls back to
'Attachment #<id>' when only the id came through.
This commit is contained in:
Joel Brock
2026-05-19 15:51:35 -07:00
parent a8ed6073a9
commit 9209d6dc02
3 changed files with 50 additions and 5 deletions
+17 -1
View File
@@ -158,15 +158,26 @@ export async function GET(req: NextRequest) {
const orgId = orgs[0].contact_id_b;
// Org name + activity walk + option groups, in parallel.
// For file-type fields, also request the joined `.file_name` so the
// report can surface a human-readable filename rather than the raw
// file id that APIv4 returns by default.
const civiFieldNames = Array.from(
new Set(allFields.map((f) => f.civiField).filter((f): f is string => !!f)),
);
const fileFieldRefs = Array.from(
new Set(
allFields
.filter((f) => f.type === "file" && f.civiField)
.map((f) => `${f.civiField!}.file_name`),
),
);
const select = [
"id",
"activity_date_time",
"subject",
ACTIVITY_STAGE_FIELD,
...civiFieldNames,
...fileFieldRefs,
];
const [orgRes, activityRes, options] = await Promise.all([
@@ -227,10 +238,15 @@ export async function GET(req: NextRequest) {
for (const row of rows) {
const v = row[f.civiField];
if (v === null || v === undefined || v === "") continue;
let value: unknown = v;
if (f.type === "file") {
const fname = row[`${f.civiField}.file_name`];
value = { id: v, file_name: typeof fname === "string" ? fname : undefined };
}
entries.push({
activityId: row.id,
date: row.activity_date_time,
value: v,
value,
});
}
if (entries.length > 0) fieldHistory[f.name] = entries;